﻿using Bettzueche.Dampfross;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace Bettzueche.DampfrossContinuum
{
    class Helper
    {
        /// <summary>
        /// Speichert die Hex-Kachel-Welt in die Datei 'filename'.
        /// </summary>
        /// <typeparam name="KType">Der Kachel-Typ</typeparam>
        /// <param name="filename">The filename.</param>
        /// <param name="map">The map.</param>
        /// <returns>
        /// true, wenn Speichern erfolgreich; false, wenn Fehler auftrat
        /// </returns>
        public static bool SafeMapToFile<KType>(string filename, HexMapRectFlat<KType> map) 
            where KType : Hexagon, new()
        {
            FileStream fs = new FileStream(filename, FileMode.Create);
            BinaryFormatter formatter = new BinaryFormatter();
            try {
                formatter.Serialize(fs, map);
                return true;
            }
            catch (SerializationException e) {
                Debug.WriteLine("Failed to serialize. Reason: " + e.Message);
                return false;
            }
            finally {
                fs.Close();
            }
        }

        /// <summary>
        /// Lädt die Hex-Kachel-Welt aus der Datei 'filename'.
        /// <para>
        /// </para>
        /// </summary>
        /// <typeparam name="KType">Der Kachel-Typ</typeparam>
        /// <param name="filename">The filename.</param>
        /// <returns>
        /// HexMap, wenn Laden erfolgreich; null, wenn Fehler auftrat
        /// </returns>
        public static HexMapRectFlat<KType> LoadMapFromFile<KType>(string filename)
            where KType : Hexagon, new()
        {
            FileStream fs = new FileStream(filename, FileMode.Open);
            HexMapRectFlat<KType> map = null;
            try {
                BinaryFormatter formatter = new BinaryFormatter();
                map = (HexMapRectFlat<KType>)formatter.Deserialize(fs);
                return map;
            }
            catch (SerializationException e) {
                Debug.WriteLine("Failed to deserialize. Reason: " + e.Message);
                return null;
            }
            finally {
                fs.Close();
            }
        }

        /// <summary>
        /// Lädt Q-Sollwerte (Optimalwerte) aus angegebener Datei.
        /// </summary>
        /// <param name="filename">The filename.</param>
        /// <returns>key: FeaturedID; Value: Sollwerte-Array</returns>
        /// <remarks>
        /// Erwartetes Format der Semikolon-separierten Datei, je Zeile:
        /// xxxfID;NO;N;NW;SW;S;SO;
        /// Mit xxx beliebig und die Werte fID, NO,... ganzzahlig positiv.
        /// </remarks>
        public static Dictionary<int, double[]> LadeSollwerte(string filename) {
            Dictionary<int, double[]> sollWerte = null;
            string fullText = "";
            try {
                //var idLines = File.ReadLines(filename);
                fullText = File.ReadAllText(filename);
            }
            catch (Exception e) {
                Debug.WriteLine("LadeSollwerte: Failed to read file. Reason: " + e.Message);
                return null;
            }
            //                      fID           NO      N     NW     SW     S      SO
            Regex rx = new Regex("(-{0,1}\\d+);(\\d+);(\\d+);(\\d+);(\\d+);(\\d+);(\\d+);\r*$", RegexOptions.Multiline | RegexOptions.IgnoreCase);// 
            MatchCollection matches = rx.Matches(fullText);
            if (matches.Count > 0) {
                sollWerte = new Dictionary<int, double[]>(matches.Count + (matches.Count >> 1));
                foreach (Match match in matches) {
                    //int[] soll_zArr = new int[6];
                    double[] soll_zArr = new double[6];
                    for (int i = 2; i <= 7; i++) {
                        //int soll_i = Int32.Parse(match.Groups[i].Value);
                        double soll_i = Double.Parse(match.Groups[i].Value);
                        soll_zArr[i-2] = soll_i;
                    }
                    int fID = Int32.Parse(match.Groups[1].Value);
                    sollWerte[fID] = soll_zArr;
                }
            }
            return sollWerte;
        }

        /// <summary>
        /// Gibs the soll mittelwerte.
        /// </summary>
        /// <param name="filename">The filename der ungemittelten Sollwerte.</param>
        /// <returns>Key=zID, Value=gemittelte Minimalkosten für Aktion NO, N,...,SO </returns>
        public static Dictionary<int, double[]> GibSollMittelwerte(string filename) {
            Dictionary<int, double[]> sollWerte = new Dictionary<int, double[]>();
            Dictionary<int, int> idCount = new Dictionary<int, int>();
            string fullText = "";
            try {
                //var idLines = File.ReadLines(filename);
                fullText = File.ReadAllText(filename);
            }
            catch (Exception e) {
                Debug.WriteLine("GibSollMittelwerte: Failed to read file. Reason: " + e.Message);
                return null;
            }
            //                      fID           NO      N     NW     SW     S      SO
            Regex rx = new Regex("(-{0,1}\\d+);(\\d+);(\\d+);(\\d+);(\\d+);(\\d+);(\\d+);\r*$", RegexOptions.Multiline | RegexOptions.IgnoreCase);// 
            MatchCollection matches = rx.Matches(fullText);
            if (matches.Count > 0) {
                foreach (Match match in matches) {
                    int id = Int32.Parse(match.Groups[1].Value);
                    double[] soll_zArr = new double[6];
                    for (int i = 2; i <= 7; i++) {
                        double soll_i = Double.Parse(match.Groups[i].Value);
                        soll_zArr[i-2] = soll_i;
                    }
                    if (sollWerte.ContainsKey(id)) {
                        double[] soll = sollWerte[id];
                        for (int i = 0; i < soll.Length; i++) {
                            soll[i] += soll_zArr[i];
                        }
                        idCount[id]++;
                    }
                    else {
                        sollWerte[id] = soll_zArr;
                        idCount[id] = 1;
                    }

                }
                // mehrfache Mitteln
                foreach (int id in idCount.Keys) {
                    int count = idCount[id];
                    if (count > 1) {
                        double[] solls = sollWerte[id];
                        for (int i = 0; i < solls.Length; i++) {
                            solls[i] = solls[i] / count;
                        }
                    }
                }
            }
            return sollWerte;
        }

        /// <summary>
        /// Erhält eine Karte aus dem diskreten Dampfross und erzeugt daraus eine kontinuierliche Variante.<para>
        /// KachelC.Content wird kopiert und erhält für MeasureBase eine Zufallszahl in [0.0, 0.1[.
        /// </para>
        /// </summary>
        /// <param name="diskreteMap">The diskrete map.</param>
        /// <returns></returns>
        /// <exception cref="System.NotImplementedException"></exception>
        public static HexMapRectFlat<KachelC> ConvertToContinuum(HexMapRectFlat<Bettzueche.Dampfross.Kachel> diskreteMap) {
            Random rand = new Random();
            HexMapRectFlat<KachelC> newMap = new HexMapRectFlat<KachelC>(diskreteMap.Height, diskreteMap.Width);
            var diskreteEnum = diskreteMap.GetEnumerator();
            var newEnum = newMap.GetEnumerator();
            while (diskreteEnum.MoveNext()) {
                if (!newEnum.MoveNext()) {
                    break;
                }
                var diskKachel = diskreteEnum.Current;
                var newKachel = newEnum.Current;
                double measureBase = rand.NextDouble() * 0.1;
                newKachel.Content = new KachelCContent(measureBase, diskKachel.Content.Typ, diskKachel.Content.FlussFlags);
            }
            return newMap;
        }

        /// <summary>
        /// Lädt die Zustands-IDs (gehashte FeaturedIDs) aus der angegebenen Datei und gibt sie als Array zurück.<para>
        /// Normalerweise solten die IDs reihenweise (zick-zack) sortiert sein und Stadt- wie innere
        /// Wasserfelder exklusive / ausgespart sein!
        /// </para>
        /// </summary>
        /// <param name="filename">The filename.</param>
        /// <returns>Zustandsnummern</returns>
        public static int[] LadeFeatureIDs(string filename) {
            List<int> idList = new List<int>();
            string idsStr = "";
            try {
                //var idLines = File.ReadLines(filename);
                idsStr = File.ReadAllText(filename);
            }
            catch (Exception e) {
                Debug.WriteLine("LadeZIDs: Failed to read file. Reason: " + e.Message);
                return null;
            }
            Regex rx = new Regex("-?\\d+\r$", RegexOptions.Multiline | RegexOptions.IgnoreCase);
            MatchCollection matches = rx.Matches(idsStr);
            if (matches.Count > 0) {
                foreach (Match match in matches) {
                    string idStr = match.Value;
                    int id = Int32.Parse(idStr);
                    idList.Add(id);
                }
            }
            return idList.ToArray();
        }
        /// <summary>
        /// Lädt aus der angegebenen Datei zujedem Zustand die Merkmalsliste und - als letztes Element - die
        /// gehashte Gesamt-FeatureID und gibt sie als Array von Arrays zurück.<para>
        /// Normalerweise solten die Zustände reihenweise (zick-zack) sortiert sein und Stadt- wie innere
        /// Wasserfelder exklusive / ausgespart sein!
        /// </para>
        /// </summary>
        /// <param name="filename">The filename.</param>
        /// <returns>Liste von Merkmalsnummern plus FeaturedID (2D Array)</returns>
        internal static int[][] LadeMerkmale(string filename) {
            List<int[]> idList = new List<int[]>();
            string idsStr = "";
            try {
                //var idLines = File.ReadLines(filename);
                idsStr = File.ReadAllText(filename);
            }
            catch (Exception e) {
                Debug.WriteLine("LadeZIDs: Failed to read file. Reason: " + e.Message);
                return null;
            }
            Regex rx = new Regex("((;\\s*\\d+)+);\\s*(-{0,1}\\d+)\r$", RegexOptions.Multiline | RegexOptions.IgnoreCase);
            Regex rx2 = new Regex(";\\s*(\\d+)", RegexOptions.IgnoreCase);
            MatchCollection matches = rx.Matches(idsStr);
            if (matches.Count > 0) {
                foreach (Match lineMatch in matches) {
                    MatchCollection fIDMatches = rx2.Matches(lineMatch.Groups[1].Value);
                    int[] idArr = new int[fIDMatches.Count + 1];
                    for (int i = 0; i < fIDMatches.Count; i++) {
                        Match fMatch = fIDMatches[i];
                        string fNumStr = fMatch.Groups[1].Value;
                        int fNum = Int32.Parse(fNumStr);
                        idArr[i] = fNum;
                    }
                    string fIDStr = lineMatch.Groups[3].Value;
                    int fID = Int32.Parse(fIDStr);
                    idArr[idArr.Length-1] = fID;
                    idList.Add(idArr);
                }
            }
            return idList.ToArray();
        }

        //public static HexMapRectFlat<Bettzueche.Dampfross.Types.Kachel> ConvertOldKachel(HexMapRectFlat<Bettzueche.Dampfross.Kachel> oldMap) {
        //    var newMap = new HexMapRectFlat<Bettzueche.Dampfross.Types.Kachel>(oldMap.Height, oldMap.Width);
        //    var oldEnum = oldMap.GetEnumerator();
        //    var newEnum = newMap.GetEnumerator();
        //    while (oldEnum.MoveNext()) {
        //        if (!newEnum.MoveNext())
        //            break;
        //        var oldKachel = oldEnum.Current;
        //        var newKachel = newEnum.Current;
        //        newKachel.Content = oldKachel.Content;
        //    }
        //    return newMap;
        //}
    }
}
